package cn.admin.remoting.impl;

import cn.admin.remoting.Connection;
import cn.admin.remoting.ConnectionFactory;
import cn.admin.remoting.IOEventListener;
import cn.admin.remoting.RequestProcessor;
import cn.admin.remoting.serialize.impl.RemotingProtocolCodecFilter;
import cn.admin.remoting.util.NamedThreadFactory;
import cn.admin.remoting.util.UnsafeCast;
import org.apache.mina.common.IoFilter;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public abstract class DefaultConnectionFactory implements ConnectionFactory {

    static final String CODEC_FILTER_NAME = "objectserialize";
    static final IoFilter CODEC_FILTER = new RemotingProtocolCodecFilter();
    static final int processorCount = Runtime.getRuntime().availableProcessors();
    static final Executor IO_THREAD_POOL =
            Executors.newCachedThreadPool(new NamedThreadFactory("",true));
    private static final String DEFAULT_GROUP_NAME = "DEFAULT_GROUP";
    private final ConcurrentHashMap<String, Set<Connection>> groups = new ConcurrentHashMap<>();
    CopyOnWriteArrayList<IOEventListener> globalListeners = new CopyOnWriteArrayList<>();
    private final ProcessorUtil globalProcessors = new ProcessorUtil();

    DefaultConnectionFactory() {
        IOEventListener.ConnectionLifecycleListener groupMaintainListener = new IOEventListener.
                ConnectionLifecycleListener() {
            @Override
            public void connectionOpened(Connection connection) {
                DefaultConnectionFactory.this.addConnectionToGroup0(connection, DEFAULT_GROUP_NAME);
            }

            @Override
            public void connectionClosed(Connection connection) {
                synchronized (DefaultConnectionFactory.this.groups) {

                    for (String group : DefaultConnectionFactory.this.groups()) {
                        DefaultConnectionFactory.this.removeConnectionFromGroup0(connection, group);
                    }

                    DefaultConnectionFactory.this.removeConnectionFromGroup0(connection, DEFAULT_GROUP_NAME);

                }
            }
        };
        this.addListener(groupMaintainListener);
        this.addListener(new CL_IdleListener());
        this.addListener(new LCL_AliveSessionsListener());
        this.registerProcessor(HeartBeatProcessor.instance);
    }

    private void addConnectionToGroup0(Connection connection,String groupName) {

        synchronized (this.groups) {
            Set<Connection> group = this.groups.computeIfAbsent(groupName, s -> new HashSet<>());
            group.add(connection);
        }

    }

    private void removeConnectionFromGroup0(Connection connection,String groupName) {

        synchronized (this.groups) {

            Set<Connection> group = this.groups.get(groupName);
            if (group == null) {
                this.groups.remove(groupName);
            } else {
                group.remove(connection);
                if (group.isEmpty()) {
                    this.groups.remove(groupName);
                }
            }

        }

    }

    private boolean checkParam(Connection connection,String groupName) {

        if (DEFAULT_GROUP_NAME.equals(groupName)) {
            return false;
        } else {
            if (connection != null) {
                synchronized (this.groups) {
                    Set<Connection> defaultGroup = this.groups.get(DEFAULT_GROUP_NAME);
                    if (defaultGroup == null || !defaultGroup.contains(connection)) {
                        return false;
                    }
                }
            }
            return true;
        }
    }

    private List<Connection> groupConnectionSnapshot0(String groupName) {

        List<Connection> conns = new ArrayList<>();
        synchronized (this.groups) {
            Set<Connection> group = this.groups.get(groupName);
            if (group != null) {
                conns.addAll(group);
            }
        }
        return conns;
    }

    @Override
    public List<String> groups() {
        List<String> result = new ArrayList<>(this.groups.keySet());
        result.remove(DEFAULT_GROUP_NAME);
        return result;
    }

    @Override
    public int connectionCountInGroup(String group) {
        Set<Connection> conns = this.groups.get(group);
        return conns == null ? 0 : conns.size();
    }

    @Override
    public boolean addConnectionToGroup(Connection connection, String group) {
        if (!this.checkParam(connection,group)) {
            return false;
        } else {
            this.addConnectionToGroup0(connection,group);
            return true;
        }
    }

    @Override
    public boolean removeConnectionFromGroup(Connection connection, String groupName) {
        if (!this.checkParam(connection,groupName)) {
            return false;
        } else {
            this.removeConnectionFromGroup0(connection,groupName);
            return true;
        }
    }

    @Override
    public List<Connection> groupConnectionSnapshot(String groupName) {
        return !this.checkParam(null,groupName) ? UnsafeCast.cast(Collections.EMPTY_LIST) :
                this.groupConnectionSnapshot0(groupName);
    }

    @Override
    public List<Connection> allConnectionSnapshot() {
        return this.groupConnectionSnapshot0(DEFAULT_GROUP_NAME);
    }

    @Override
    public final void addListener(IOEventListener listener) {
        this.globalListeners.add(listener);
    }

    @Override
    public final void removeListener(IOEventListener listener) {
        this.globalListeners.remove(listener);
    }

    @Override
    public void registerProcessor(RequestProcessor<?> processor) {
        this.globalProcessors.registerProcessor(processor);
    }

    @Override
    public void replaceProcessor(RequestProcessor<?> processor) {
        this.globalProcessors.removeProcessor(processor.interest());
        this.globalProcessors.registerProcessor(processor);
    }

    @Override
    public <T> RequestProcessor<T> removeProcessor(Class<T> appRequestClazz) {
        return this.globalProcessors.removeProcessor(appRequestClazz);
    }

    @Override
    public <T> RequestProcessor<T> findProcessor(Class<T> appRequestClazz) {
        return this.globalProcessors.findProcessor(appRequestClazz);
    }

    @Override
    public Map<Class<?>, RequestProcessor<?>> getProcessors() {
        return this.globalProcessors.getProcessors();
    }
}
